#import "CryptoPlugin.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCrypto.h>

#import "FXCryptoSecurity.h"

@implementation CryptoPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  FlutterMethodChannel* channel = [FlutterMethodChannel
      methodChannelWithName:@"com.jacky.util/crypto"
            binaryMessenger:[registrar messenger]];
  CryptoPlugin* instance = [[CryptoPlugin alloc] init];
  [registrar addMethodCallDelegate:instance channel:channel];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  NSString* method = call.method;

    if ([@"md5String" isEqualToString:method]) {
        NSString* p = call.arguments;
        result([self stringToMD5:p]);
    } else if([@"md5File" isEqualToString:method]) {
        NSString* path = call.arguments;
        NSString* res =
              (__bridge_transfer NSString *)
                      FileMD5HashCreateWithPath((__bridge CFStringRef)path);
        if(res == nil) {
            result(@"");
        } else {
            result(res);
        }
    } else if([@"md5Byte" isEqualToString:method]) {
        FlutterStandardTypedData *p = call.arguments;
        const char* bbb = [p.data bytes];
        int size = p.elementCount;
        //char i[size];
        //memcpy(i,bbb ,size);
        result([self charToMD5:bbb length:size]);
    } else if([@"aesEncrypt" isEqualToString:method]) {
        NSString* content = call.arguments[@"content"];
        NSString* password = call.arguments[@"password"];
        BOOL hex = [call.arguments[@"hex"] boolValue];
        NSString* res = [self encryptAES:content key:password hex:hex];
//        NSString* res = [FXCryptoSecurity aes256_encrypt:password text:content];
        if(res == nil) {
            result(@"");
        } else {
            result(res);
        }
    } else if([@"aesDecrypt" isEqualToString:method]) {
        NSString* content = call.arguments[@"content"];
        NSString* password = call.arguments[@"password"];
        BOOL hex = [call.arguments[@"hex"] boolValue];
        NSString* res = [self decryptAES:content key:password hex:hex];
        
//        NSString* res = [FXCryptoSecurity aes256_decrypt:password text:content];
        if(res == nil) {
            result(@"");
        } else {
            result(res);
        }
    } else if([@"macsha256" isEqualToString:method]) {
                NSString* content = call.arguments[@"content"];
                NSString* password = call.arguments[@"password"];
                BOOL hex = [call.arguments[@"hex"] boolValue];
                //NSString* res = [self decryptAES:content key:password hex:hex];
                //if(res == nil) {
                   result(@"");
                //} else {
                //    result(res);
               // }
   } else if ([@"sha256String" isEqualToString:method]) {
           NSString* p = call.arguments;
           result(@"");
       } else if([@"sha256File" isEqualToString:method]) {
           NSString* path = call.arguments;
           NSString *res = [FXCryptoSecurity sha256HashOfFileAtPath:path];
           //NSString* res =
           //      (__bridge_transfer NSString *)
            //             FileMD5HashCreateWithPath((__bridge CFStringRef)path);
           //if(res == nil) {
           if(res == nil) {
               result(@"");
           } else {
               result(res);
           }
          // } else {
           //    result(@"");
           //}
    } else if([@"rsaEncrypt" isEqualToString:method]) {
        NSString* content = call.arguments[@"content"];
        NSString* key = call.arguments[@"key"];
        NSString* res = [FXCryptoSecurity encryptString:content publicKey:key];
        if(res == nil) {
            result(@"");
        } else {
            result(res);
        }
    } else if([@"rsaDecrypt" isEqualToString:method]) {
        NSString* content = call.arguments[@"content"];
        NSString* key = call.arguments[@"key"];
        NSString* res = [FXCryptoSecurity decryptString:content publicKey:key];
        if(res == nil) {
            result(@"");
        } else {
            result(res);
        }
    } else {
        NSLog(@"Method %@ not support", method);
        result(FlutterMethodNotImplemented);
    }
}

- (NSString *)stringToMD5:(NSString *)str {
    const char *cStr = [str UTF8String];
    return [self charToMD5:cStr length:(CC_LONG)strlen(cStr)];
}

- (NSString *)charToMD5:(const char *)cStr  length:(CC_LONG) length {
    unsigned char digest[CC_MD5_DIGEST_LENGTH];
    CC_MD5(cStr, length, digest);
    NSMutableString *result = [NSMutableString stringWithCapacity:
                               CC_MD5_DIGEST_LENGTH * 2];
    for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
        [result appendFormat:@"%02x", digest[i]];
    return result;
}

- (NSString *)encryptAES:(NSString *)content key:(NSString *)key hex:(BOOL)hex {

    char keyPtr[kCCKeySizeAES128+1];
    memset(keyPtr, 0, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSData* data = [content dataUsingEncoding:NSUTF8StringEncoding];
    NSUInteger dataLength = [data length];

    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding,
                                          keyPtr,
                                          kCCBlockSizeAES128,
                                          NULL,
                                          [data bytes],
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &numBytesEncrypted);
    
    if (cryptStatus == kCCSuccess) {
        NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];

        if(hex) {
            Byte *datas = (Byte *)[resultData bytes];

            NSMutableString *outPut = [NSMutableString stringWithCapacity:resultData.length];

            for(int i = 0 ; i < resultData.length ; i++)
            {
                [outPut appendFormat:@"%02x",datas[i]];
            }
            return outPut;
        } else {
            return [resultData base64EncodedStringWithOptions:
                                    NSDataBase64EncodingEndLineWithLineFeed];
        }
    }
    free(buffer);
    return nil;
}

- (NSString *)decryptAES:(NSString *)content key:(NSString *)key hex:(BOOL)hex {
    NSData *data;
    if(hex) {
        NSUInteger length = [content length] /2;
        NSMutableData *dddd = [NSMutableData dataWithCapacity:length];
        unsigned char whole_bytes;

        char byte_chars[3] = {'\0','\0','\0'};
        for(int i = 0 ; i < length; i++)
        {
            byte_chars[0] = [content characterAtIndex:i * 2];
            byte_chars[1] = [content characterAtIndex:i * 2 + 1];
            whole_bytes = strtol(byte_chars, NULL, 16);
            [dddd appendBytes:&whole_bytes length:1];
        }
        data = dddd;
    } else {
        //base64解码
        data = [[NSData alloc] initWithBase64EncodedString:content options:NSDataBase64DecodingIgnoreUnknownCharacters];
    }

    char keyPtr[kCCKeySizeAES128 + 1];
    memset(keyPtr, 0, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [data length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesCrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding,
                                          keyPtr,
                                          kCCBlockSizeAES128,
                                          NULL,
                                          [data bytes],
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &numBytesCrypted);
    if (cryptStatus == kCCSuccess) {
        NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
        return [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
    }
    free(buffer);
    return nil;
}

CFStringRef FileMD5HashCreateWithPath(CFStringRef filePath) {
    // Declare needed variables
    CFStringRef result = NULL;
    CFReadStreamRef readStream = NULL;

    // Get the file URL
    CFURLRef fileURL =
    CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
                                  (CFStringRef)filePath,
                                  kCFURLPOSIXPathStyle,
                                  (Boolean)false);
    if (!fileURL) goto done;

    // Create and open the read stream
    readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault,
                                            (CFURLRef)fileURL);
    if (!readStream) goto done;
    bool didSucceed = (bool)CFReadStreamOpen(readStream);
    if (!didSucceed) goto done;

    // Initialize the hash object
    CC_MD5_CTX hashObject;
    CC_MD5_Init(&hashObject);

    // Feed the data to the hash object
    bool hasMoreData = true;
    while (hasMoreData) {
        uint8_t buffer[1024];
        CFIndex readBytesCount = CFReadStreamRead(readStream,
                                                  (UInt8 *)buffer,
                                                  (CFIndex)sizeof(buffer));
        if (readBytesCount == -1) break;
        if (readBytesCount == 0) {
            hasMoreData = false;
            continue;
        }
        CC_MD5_Update(&hashObject,
                      (const void *)buffer,
                      (CC_LONG)readBytesCount);
    }
    // Check if the read operation succeeded
    didSucceed = !hasMoreData;
    // Compute the hash digest
    unsigned char digest[CC_MD5_DIGEST_LENGTH];
    CC_MD5_Final(digest, &hashObject);

    // Abort if the read operation failed
    if (!didSucceed) goto done;

    // Compute the string result
    char hash[2 * sizeof(digest) + 1];
    for (size_t i = 0; i < sizeof(digest); ++i) {
        snprintf(hash + (2 * i), 3, "%02x", (int)(digest[i]));
    }
    result = CFStringCreateWithCString(kCFAllocatorDefault,
                                       (const char *)hash,
                                       kCFStringEncodingUTF8);
done:
    if (readStream) {
        CFReadStreamClose(readStream);
        CFRelease(readStream);
    }
    if (fileURL) {
        CFRelease(fileURL);
    }
    return result;
}
@end
